<template>
  <div class="uploader">
    <div class="uploader-title">
      <div class="icon">上传文件 <span> (仅展示本次上传任务)</span></div>
    </div>
    <div class="uploader-content">
      <div class="file-item" v-for="(i, index) in fileList" :key="index">
        <div class="upload-panle">
          <div class="file-item-content">
            <div class="file-name">{{ i.fileName }}</div>
          </div>
          <div class="file-progress">
            <el-progress
              :percentage="i.uploadProgress"
              v-if="
                i.status == STATUS.upload_seconds.value ||
                i.status == STATUS.uploading.value ||
                i.status == STATUS.upload_finish.value
              "
            ></el-progress>
          </div>
          <div class="file-status">
            <div class="status-text">
              <el-icon class="status-icon">
                <component :is="STATUS[i.status].icon" />
              </el-icon>
              {{ STATUS[i.status].desc }}
            </div>
            <div
              class="fileSize_upload"
              v-if="i.status == STATUS.uploading.value"
            >
              <div class="fileSize_upload">
                {{ formatBytes(i.uploadSize) }} / {{ formatBytes(i.totalSize) }}
              </div>
            </div>
          </div>
        </div>
        <div class="file-action">
          <el-progress
            :percentage="i.md5progress"
            v-if="i.status == STATUS.init.value"
            type="circle"
            :width="50"
          ></el-progress>
          <div class="op-buttun">
            <div v-if="i.status === STATUS.uploading.value">
              <Icon
                :width="32"
                iconName="upload"
                title="上传"
                v-if="i.pause"
                @click="startUpload(i)"
              ></Icon>
              <Icon
                v-else
                :width="32"
                iconName="pause"
                title="暂停"
                @click="pauseUpload(i)"
              ></Icon>
            </div>
            <div>
              <Icon
                :width="32"
                iconName="del"
                title="删除"
                v-if="
                  i.status != STATUS.init.value &&
                  i.status != STATUS.upload_finish.value &&
                  i.status != STATUS.upload_seconds.value
                "
                @click="delUpload(i.uid, index)"
              ></Icon>
              <Icon
                :width="32"
                iconName="clean"
                title="清除"
                v-if="
                  i.status == STATUS.upload_finish.value ||
                  i.status == STATUS.upload_seconds.value
                "
                @click="delUpload((i.uid = index))"
              ></Icon>
            </div>
          </div>
        </div>
      </div>

      <div class="Nodata" v-if="!fileList.length">
        <div>
          <Icon :width="200" iconName="emptyfile" title="空" fit="fill"></Icon>
          <div class="no-data-message">暂无上传任务</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import SparkMD5 from "spark-md5";
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
import formatBytes from "@/utils/bit.js";
import { cutFile } from "@/utils/creatChunk.js";
const { proxy } = getCurrentInstance();

const api = {
  upload: "/file/uploadFile",
};
const chunkSize = 1024 * 1024 * 5;
const emit = defineEmits(["uploadCallback"]);
const fileList = ref([]);
const delList = ref([]);
const STATUS = {
  init: {
    value: "init",
    desc: "解析中",
    icon: "Loading",
  },
  upload_finish: {
    value: "upload_finish",
    desc: "上传成功",
    icon: "SuccessFilled",
  },
  fail: {
    value: "fail",
    desc: "上传失败",
    icon: "WarningFilled",
  },
  emptyfile: {
    value: "emptyfile",
    desc: "文件为空",
    icon: "InfoFilled",
  },
  uploading: {
    value: "uploading",
    desc: "上传中",
    icon: "Wallet",
  },
  upload_seconds: {
    value: "upload_seconds",
    desc: "秒传成功",
    icon: "SuccessFilled",
  },
};
const startUpload = (file) => {
  file.pause = !file.pause;
  uploadfile(file.uid, file.chunkIndex);
};
const pauseUpload = (file) => {
  file.pause = !file.pause;
};
const addfile = async (file, filePid) => {
  const fileItem = {
    //文件对象
    file,
    // 上传状态
    status: STATUS.init.value,
    // 已上传大小
    uploadSize: 0,
    // 总大小
    totalSize: file.size,
    // 上传进度
    uploadProgress: 0,
    // 是否暂停
    pause: false,
    // 文件uid
    uid: file.uid,
    // MD5值
    md5: null,
    // MD5进度
    md5progress: 0,
    // 文件名
    fileName: file.name,
    // 当前分片
    currentIndex: 0,
    // 文件父级id
    filePid,
    // 错误信息
    errorMsg: null,
  };
  fileList.value.unshift(fileItem);
  if (fileItem.totalSize == 0) {
    fileItem.status = STATUS.emptyfile.value;
    return;
  }
  const res = cutFile(fileItem);
  console.log(res);

  let md5fileId = await computedMD5(fileItem);
  // debugger;
  if (md5fileId == null) {
    return;
  }
  uploadfile(md5fileId);
};

const uploadfile = async (uid, chunkIndex = 0) => {
  chunkIndex = chunkIndex ? chunkIndex : 0;
  let currentfile = getfileByUid(uid);

  const file = currentfile.file;
  const fileSize = currentfile.totalSize;
  const chunks = Math.ceil(fileSize / chunkSize);
  for (let i = chunkIndex; i < chunks; i++) {
    let delIndex = delList.value.indexOf(uid);
    if (delIndex != -1) {
      delList.value.splice(delIndex, 1);
      break;
    }
    currentfile = getfileByUid(uid);
    if (currentfile.pause) {
      break;
    }
    let start = i * chunkSize;
    let end = start + chunkSize >= fileSize ? fileSize : start + chunkSize;
    let chunkfile = file.slice(start, end);
    // 构建请求参数
    let params = {
      //上传分片的文件
      file: chunkfile,
      //被分片的文件的文件名
      fileName: file.name,
      //总分片
      chunks,
      // 分片索引
      chunkIndex: i,
      //文件md5
      fileMd5: currentfile.md5,
      // 文件pid(父级id)
      filePid: currentfile.filePid,
    };

    // 如果 currentfile.fileId 存在，则添加到请求参数中
    if (currentfile.fileId) {
      params.fileId = currentfile.fileId;
    }
    let updateRequest = await proxy.request({
      url: api.upload,
      showLoading: false,
      dataType: "file",
      params,
      showlError: false,
      errorCallback: (errormsg) => {
        currentfile.status = STATUS.fail.value;
        currentfile.errorMsg = errormsg;
      },
      uploadProgressCallback: (e) => {
        let loaded = e.loaded;
        if (loaded > fileSize) {
          loaded = fileSize;
        }
        currentfile.uploadSize = loaded + i * chunkSize;
        currentfile.uploadProgress = Math.floor(
          (currentfile.uploadSize / fileSize) * 100
        );
      },
    });
    if (!updateRequest) {
      break;
    }
    currentfile.fileId = updateRequest.data.fileId;

    currentfile.status = STATUS[updateRequest.data.status].value;
    currentfile.chunkIndex = i;
    if (
      updateRequest.data.status == STATUS.upload_seconds.value ||
      updateRequest.data.status == STATUS.upload_finish.value
    ) {
      currentfile.uploadProgress = 100;
      emit("uploadCallback");
      break;
    }
  }
};
const computedMD5 = async (fileitem) => {
  let file = fileitem.file;
  let blobSlice =
    File.prototype.slice ||
    File.prototype.mozSlice ||
    File.prototype.webkitSlice;
  let chunks = Math.ceil(file.size / chunkSize);
  let currentChunk = 0;
  let spark = new SparkMD5.ArrayBuffer();
  let fileReader = new FileReader();
  /**
   * 1. blobSlice.call(file, start, end)
            blobSlice: 这是一个方法引用，通常指向 File.prototype.slice、File.prototype.mozSlice 或 File.prototype.webkitSlice。这些方法用于从文件中提取指定范围的数据。
            file: 这是要读取的文件对象。
            start: 这是读取数据的起始位置（字节索引）。
            end: 这是读取数据的结束位置（字节索引）。注意，end 是不包含的，即读取的数据范围是 [start, end)。
    2. fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
            fileReader: 这是一个 FileReader 对象，用于读取文件内容。
            readAsArrayBuffer: 这是 FileReader 的一个方法，用于将文件的指定部分读取为 ArrayBuffer 对象。ArrayBuffer 是一种用于表示通用、固定长度的原始二进制数据缓冲区的对象。
   */
  let loadNext = () => {
    let start = currentChunk * chunkSize;
    let end = start + chunkSize >= file.size ? file.size : start + chunkSize;
    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
  };
  loadNext();

  return new Promise((resolve, reject) => {
    let resultfile = getfileByUid(file.uid);
    fileReader.onload = (e) => {
      spark.append(e.target.result);
      currentChunk++;
      if (currentChunk < chunks) {
        let percent = Math.floor((currentChunk / chunks) * 100);
        resultfile.md5progress = percent;
        loadNext();
      } else {
        let md5 = spark.end();
        spark.destroy();
        resultfile.md5progress = 100;
        resultfile.status = STATUS.uploading.value;
        resultfile.md5 = md5;
        resolve(fileitem.uid);
      }
    };
    fileReader.onerror = () => {
      resultfile.md5progress = -1;
      resultfile.status = STATUS.fail.value;
      resultfile.errorMsg = "读取文件失败";
      reject(fileitem.uid);
    };
  }).catch((err) => {
    console.log(err);
    return null;
  });
};

const getfileByUid = (uid) => {
  return fileList.value.find((item) => item.uid === uid);
};

const getStatusClass = (status) => {
  switch (status) {
    case STATUS.init.value:
      return "status-init";
    case STATUS.upload_finish.value:
      return "status-success";
    case STATUS.fail.value:
      return "status-error";
    case STATUS.emptyfile.value:
      return "status-empty";
    case STATUS.uploading.value:
      return "status-uploading";
    case STATUS.upload_seconds.value:
      return "status-seconds";
    default:
      return "";
  }
};
defineExpose({
  addfile,
});
</script>

<style lang="scss" scoped>
.uploader {
  min-height: 400px;
  max-height: 600px;
  overflow: auto;
}

.upload-panle {
  display: flex;
  margin-right: auto;
  width: 100%;
  justify-content: space-between; /* 内容两端对齐 */
  flex-direction: column;
}
.uploader-title {
  justify-content: space-between;
  font-size: 18px; /* 标题字体大小 */
  font-weight: bold; /* 加粗 */
  margin-bottom: 12px; /* 标题与内容之间的间距 */
  .icon {
    display: flex;
    margin-left: 5px;
    margin-top: 5px;
    &::before {
      content: ""; // 必须定义 content 属性
      display: block; // 使用 block 或 inline-block 保证尺寸生效
      width: 4px;
      height: 26px;
      margin-right: 5px;

      background-color: #14a8ec; // 默认黑色矩形（可自定义颜色）
    }
  }
}

.file-item {
  display: flex; /* 使用flex布局 */
  align-items: center;
  padding: 10px; /* 内边距 */
  border-bottom: 1px solid #e0e0e0; /* 分隔线 */
  .file-action {
    width: 12%;
    .op-buttun {
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
  }
}

.file-item-content {
  display: flex; /* 使用flex布局 */
  align-items: center; /* 垂直居中 */
}

.file-name {
  margin-bottom: 5px; /* 文件名与进度条之间的间距 */
  font-weight: 500; /* 文件名加粗 */
}

.file-progress {
  flex-grow: 1; /* 进度条占据剩余空间 */
  margin-right: 30px;
}

.file-status {
  font-size: 14px; /* 状态字体大小 */
  font-weight: 500; /* 状态加粗 */
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 5px;
  margin-right: auto;
  .status-icon {
    margin-right: 10px;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .fileSize_upload {
    margin-left: 10px;
  }
}

.status-init {
  color: #33abf5;
}

.status-success {
  color: #69ec88;
}
.status-error {
  color: #f32e2e;
}
.status-empty {
  color: #ffffff;
}
.status-uploading {
  color: #000000;
}
.status-seconds {
  color: #13f2ee;
}
.status-text {
  display: flex;
  align-items: center;
}
.Nodata {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #909399;
  font-size: 16px;
  margin-top: 70px;
  text-align: center;
}

.no-data-icon {
  margin-right: 8px;
  font-size: 24px;
}
.no-data-message {
  font-weight: bold;
}
</style>
